﻿
namespace DatabaseDoc.Parameters
{    
    using System.Collections.Generic;    
    using System.Text;

    /// <summary>
    /// Parameter configuration
    /// </summary>
    public class ParameterConfiguration
    {
        public Dictionary<string, ParameterDetails> Parameters { get; private set; }

        public Dictionary<ParameterDependency, DependencyRequirement> Dependencies { get; private set; }

        public delegate void ParameterAcceptanceDelegate(Parameter param, IEnumerable<Parameter> otherParameters);

        public string[] Arguments { get; private set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="ParameterConfiguration"/> class.
        /// </summary>
        /// <param name="arguments">The arguments.</param>
        /// <param name="paramPrefix">The param prefix.</param>
        /// <param name="optionPrefix">The option prefix.</param>
        /// <param name="optionSeparator">The option separator.</param>
        /// <param name="optionAssigner">The option assigner.</param>
        /// <param name="helpParamName">Name of the help param.</param>
        public ParameterConfiguration(
            string[] arguments, 
            string paramPrefix = "/", 
            string optionPrefix = ":", 
            string optionSeparator = ",", 
            string optionAssigner = "=", 
            string helpParamName = "?")
        {
            this.Arguments = arguments;

            this.Parameters = new Dictionary<string, ParameterDetails>();
            this.Dependencies = new Dictionary<ParameterDependency, DependencyRequirement>();

            this.ParamPrefix = paramPrefix;
            this.OptionPrefix = optionPrefix;
            this.OptionSeparator = optionSeparator;
            this.OptionAssigner = optionAssigner;
            this.HelpParameterName = helpParamName;
        }        

        public string ParamPrefix { get; private set; }

        public string OptionPrefix { get; set; }

        public string OptionSeparator { get; set; }

        public string OptionAssigner { get; set; }

        public string HelpParameterName { get; set; }

        public void AddParameter(string parameterName, ParameterRequirement parameterRequirement, ParameterOptions parameterOptions)
        {
            if (this.Parameters.ContainsKey(parameterName))
            {
                throw new InvalidParametersException("Parameter name already added");
            }

            this.Parameters.Add(parameterName, new ParameterDetails { ParameterRequirement = parameterRequirement, Options = parameterOptions });
        }

        // These take precendence over AddParameter requirement 
        public void AddParameterDependency(string parameterName, string dependencyParameterName, DependencyRequirement requirement)
        {
            if (!this.Parameters.ContainsKey(parameterName))
            {
                throw new InvalidParametersException(string.Format("Parameter {0} doesn't exist", parameterName));
            }

            var parameterDependency = new ParameterDependency { ParameterName = parameterName, DependencyParameterName = dependencyParameterName };

            if (this.Dependencies.ContainsKey(parameterDependency))
            {
                throw new InvalidParametersException("Parameter dependency already added");
            }

            this.Dependencies.Add(parameterDependency, requirement);            
        }

        public void AddParameterRule(string parameterName, ParameterAcceptanceDelegate acceptance)
        {
            if (!this.Parameters.ContainsKey(parameterName))
            {
                throw new InvalidParametersException(string.Format("Parameter {0} doesn't exist", parameterName));
            }

            var parameter = this.Parameters[parameterName];

            if (parameter.ParameterAcceptance != null)
            {
                throw new InvalidParametersException(string.Format("Parameter acceptance already defined for parameter {0}", parameterName));
            }

            parameter.ParameterAcceptance = acceptance;
        }

        public string GetParameterList()
        {
            if (this.Parameters == null)
            {
                return null;
            }

            var sb = new StringBuilder();
            foreach (var parameter in this.Parameters)
            {
                var parameterName = parameter.Key;
                var parameterRequirements = parameter.Value;

                sb.AppendFormat(
                    parameterRequirements.ParameterRequirement == ParameterRequirement.Required
                        ? "{0}{1}{2}"
                        : "[{0}{1}{2}]",
                    this.ParamPrefix,
                    parameterName,
                    this.GetOptions(parameter));

                sb.Append(" ");
            }

            return sb.ToString();
        }

        private string GetOptions(KeyValuePair<string, ParameterDetails> parameter)
        {
            var options = parameter.Value.Options;

            if (options.Requirement == OptionsRequirement.NotValid)
            {
                return null;
            }

            if (options.HasSingleOption)
            {
                var optionString = string.Format("{0}<{1}>", this.OptionPrefix, options.Option.Name);
                return optionString;
            }

            if (options.HasMultipleOptions)
            {
                var sb = new StringBuilder();
                foreach (var option in options.Options)
                {
                    sb.AppendFormat("{0}{1}{2}", option.Name, this.OptionAssigner, option.Value);
                }

                var optionString = string.Format("{0}<{1}>", this.OptionPrefix, sb);
                return optionString;
            }

            return null;
        }

        // TODO: support configuration of examples
        // Collection of paramters with example text, and an example description        
        public IEnumerable<string> GetExamples()
        {
            return new[]
                {
                    "databasedoc /s:MY-SERVER\\MY-INSTANCE /d:MyDatabase /o:BASICHTML /f:test",
                    "databasedoc /s:MY-SERVER\\MY-INSTANCE /d:MyDatabase /rv",
                    "databasedoc /s:MY-SERVER\\MY-INSTANCE /d:MyDatabase /rv:1.7.0.1-1.8",
                    "databasedoc /s:MY-SERVER\\MY-INSTANCE /d:MyDatabase /rv:1.7.0.1-*",
                    "databasedoc /s:MY-SERVER\\MY-INSTANCE /d:MyDatabase /rv:*-1.8.5.0",
                    "databasedoc /s:MY-SERVER\\MY-INSTANCE /d:MyDatabase /rv:1.6.0.5-1.8.1.0 /g[:basescript.sql]"
                };
        }
    }
}